/*
 * Engine.java
 *
 * The main goal of this class is to provide tools
 * to analyse the position, and give replies as a
 * virtual opponent
 * 
 */
package model;

import java.util.ArrayList;

/**
 * 
 * @author Olivier Herau
 */
public class Engine {
	
	private static double[] coefficient = { 1.0, 2.5, 2.5, 5.0, 9.0, 100000.0, -1.0,
			-2.5, -2.5, -5.0, -9.0, -100000.0 };

	/**
	 * Evaluate the score of the position used by the Alpha-Beta
	 * algorithm
	 * 
	 * @param board
	 * @return
	 */
	static float evaluate(Board board) {
		float evaluation = 0.0f;
		// TODO improve the evaluation method

		// Basic evaluation : just count the pieces for ech side with coefs
		int[] pieces = getPiecesLeft(board);
		for (int i = 0; i < 12; i++) {
			evaluation += pieces[i] * coefficient[i];
		}
		
		

		return evaluation;
	}

	/**
	 * Get the pieces left on the board
	 * 
	 * @param board
	 * @return
	 */
	private static int[] getPiecesLeft(Board board) {
		int[] nbpieces = new int[12];
		Piece piece = null;
		for (int i = 0; i < 12; i++) {
			nbpieces[i] = 0;
		}
		for (int i = 0; i < 64; i++) {
			piece = board.getPiece(i);
			if(piece != Piece.EMPTY){
				nbpieces[board.getPiece(i).ordinal()]++;
			}
		}

		return nbpieces;
	}

	/**
	 * Give the list of possible legal moves
	 * TODO : simplify the code
	 * @param board
	 * @return
	 */
	private static ArrayList<Move> getPossibleMoves(Board board) {
		ArrayList<Move> moves = new ArrayList<Move>();

		Square square;
		for(int i=0;i<64;i++)
		{
			square = Square.get(i);
			ArrayList<Move> pieceMoves = new ArrayList<Move>();
			if (!board.isEmptyOrOpponent(square)) {
				pieceMoves = getPossibleMoves(board, square);
				moves.addAll(pieceMoves);
			}
		}
		return moves;
	}


	/**
	 * Indicate if the position is check
	 * for the given player
	 * @param board
	 * @return
	 */
	public static boolean isCheck(Board board) {
		ArrayList<Move> legalMoves = getPossibleMoves(board);
		for(Move move : legalMoves){
			Board testPosition = new Board(board);
			testPosition.Play(move);
			int[] piecesLeft = getPiecesLeft(testPosition);
			// Test if a king was captured during the move 
			// WK index = 5, BK index = 5+6 (offset)
			if( piecesLeft[5+6*board.getTurn()]== 0) {return true;}			
		}
		return false;
	}

	/**
	 * Indicate if the position is checkmate
	 * 
	 * @param board
	 * @return
	 */
	public static boolean isCheckmate(Board board) {
		return (isCheck(board) && getLegalMoves(board).isEmpty());
	}

	/**
	 * Indicate if the position is stalemate
	 * 
	 * @param board
	 * @return
	 */
	public static boolean isStalemate(Board board) {
		return ((!isCheck(board)) && getPossibleMoves(board).isEmpty());

	}

	/**
	 * Get the possible moves for one piece
	 * 
	 * @param board
	 * @param square
	 * @return
	 */
	private static ArrayList<Move> getPossibleMoves(Board board, Square square) {
		ArrayList<Move> possibleMoves = new ArrayList<Move>();
		Piece pieceCode = board.getPiece(square);

		if ((board.isBlackToMove() && board.isPieceBlack(pieceCode))
				|| (board.isWhiteToMove() && board.isPieceWhite(pieceCode))) {

			switch (pieceCode) {
			case WP:
			case BP:
				possibleMoves.addAll(getPossiblePawnMoves(board, square));
				break;
			case WB:
			case BB:
				possibleMoves.addAll(getPossibleBishopMoves(board, square));
				break;
			case WN:
			case BN:
				possibleMoves.addAll(getPossibleKnightMoves(board, square));
				break;
			case WR:
			case BR:
				possibleMoves.addAll(getPossibleRookMoves(board, square));
				break;
			case WQ:
			case BQ:
				possibleMoves.addAll(getPossibleQueenMoves(board, square));
				break;
			case WK:
			case BK:
				possibleMoves.addAll(getPossibleKingMoves(board, square));
				break;
			default:
				break;

			}
		}
		return possibleMoves;
	}

	/**
	 * Get possible moves for King
	 * 
	 * @param board
	 * @param square
	 * @return
	 */
	private static ArrayList<Move> getPossibleKingMoves(Board board, Square square) {
		ArrayList<Move> possibleKingMoves = new ArrayList<Move>();
		int col = square.getCol();
		int row = square.getRow();
		// int piece = position.getPiece(col, row);

		if (col < 7 && board.isEmptyOrOpponent(Square.get(col + 1, row))) {
			Move move = new Move(square, Square.get(col + 1, row));
			possibleKingMoves.add(move);
		}
		if (col < 7 && row < 7
				&& board.isEmptyOrOpponent(Square.get(col + 1, row + 1))) {
			Move move = new Move(square, Square.get(col + 1,
					row + 1));
			possibleKingMoves.add(move);
		}
		if (row < 7 && board.isEmptyOrOpponent(Square.get(col, row + 1))) {
			Move move = new Move(square, Square.get(col, row + 1));
			possibleKingMoves.add(move);
		}
		if (col > 0 && row < 7
				&& board.isEmptyOrOpponent(Square.get(col - 1, row + 1))) {
			Move move = new Move(square, Square.get(col - 1,
					row + 1));
			possibleKingMoves.add(move);
		}
		if (col > 0 && board.isEmptyOrOpponent(Square.get(col - 1, row))) {
			Move move = new Move(square, Square.get(col - 1, row));
			possibleKingMoves.add(move);
		}
		if (col > 0 && row > 0
				&& board.isEmptyOrOpponent(Square.get(col - 1, row - 1))) {
			Move move = new Move(square, Square.get(col - 1,
					row - 1));
			possibleKingMoves.add(move);
		}
		if (row > 0 && board.isEmptyOrOpponent(Square.get(col, row - 1))) {
			Move move = new Move(square, Square.get(col, row - 1));
			possibleKingMoves.add(move);
		}
		if (col < 7 && row > 0
				&& board.isEmptyOrOpponent(Square.get(col + 1 , row - 1))) {
			Move move = new Move(square, Square.get(col + 1,
					row - 1));
			possibleKingMoves.add(move);
		}

		// TODO implement Casteling moves
		
		

		return possibleKingMoves;
	}

	/**
	 * Get possible moves for Queen
	 * 
	 * @param board
	 * @param square
	 * @return
	 */
	private static ArrayList<Move> getPossibleQueenMoves(Board board, Square square) {
		ArrayList<Move> possibleQueenMoves = new ArrayList<Move>();
		possibleQueenMoves.addAll(getPossibleRookMoves(board, square));
		possibleQueenMoves.addAll(getPossibleBishopMoves(board, square));
		return possibleQueenMoves;
	}

	/*
	 * Get possible moves for Rook
	 */
	private static ArrayList<Move> getPossibleRookMoves(Board board, Square square) {
		ArrayList<Move> possibleRookMoves = new ArrayList<Move>();
		int col = square.getCol();
		int row = square.getRow();

		// Horizontal moves right
		for (int i = 1; i < 8; i++) {
			if ((col + i) < 8
					&& board.isEmptyOrOpponent(Square.get(col + i, row))) {
				Move move = new Move(square, Square.get(col + i, row));
				possibleRookMoves.add(move);
				if (!board.isSquareEmpty(Square.get(col + i, row))) {
					i = 999;
				}
			} else {
				i = 999;
			}
		}

		// Horizontal moves left
		for (int i = 1; i < 8; i++) {
			if ((col - i) > -1
					&& board.isEmptyOrOpponent(Square.get(col - i, row))) {
				Move move = new Move(square, Square.get(col - i, row));
				possibleRookMoves.add(move);
				if (!board.isSquareEmpty(Square.get(col - i, row))) {
					i = 999;
				}
			} else {
				i = 999;
			}
		}

		// Vertical moves up
		for (int i = 1; i < 8; i++) {
			if ((row + i) < 8
					&& board.isEmptyOrOpponent(Square.get(col, row + i))) {
				Move move = new Move(square, Square.get(col, row + i));
				possibleRookMoves.add(move);
				if (!board.isSquareEmpty(Square.get(col, row + i))) {
					i = 999;
				}
			} else {
				i = 999;
			}
		}
		// Vertical moves down
		for (int i = 1; i < 8; i++) {
			if ((row - i) > -1
					&& board.isEmptyOrOpponent(Square.get(col, row - i))) {
				Move move = new Move(square, Square.get(col, row - i));
				possibleRookMoves.add(move);
				if (!board.isSquareEmpty(Square.get(col, row - i))) {
					i = 999;
				}
			} else {
				i = 999;
			}
		}

		return possibleRookMoves;
	}

	/**
	 * Get possible moves for Knight
	 * 
	 * @param board
	 * @param square
	 * @return
	 */
	private static ArrayList<Move> getPossibleKnightMoves(Board board, Square square) {
		ArrayList<Move> possibleKnightMoves = new ArrayList<Move>();
		int col = square.getCol();
		int row = square.getRow();
		// int piece = position.getPiece(col, row);

		if (col > 0 && row < 6
				&& board.isEmptyOrOpponent(Square.get(col - 1, row + 2))) {
			Move move = new Move(square, Square.get(col - 1, row + 2));
			possibleKnightMoves.add(move);
		}
		if (col < 7 && row < 6
				&& board.isEmptyOrOpponent(Square.get(col + 1, row + 2))) {
			Move move = new Move(square, Square.get(col + 1, row + 2));
			possibleKnightMoves.add(move);
		}
		if (col < 6 && row < 7
				&& board.isEmptyOrOpponent(Square.get(col + 2, row + 1))) {
			Move move = new Move(square, Square.get(col + 2, row + 1));
			possibleKnightMoves.add(move);
		}
		if (col < 6 && row > 0
				&& board.isEmptyOrOpponent(Square.get(col + 2, row - 1))) {
			Move move = new Move(square, Square.get(col + 2, row - 1));
			possibleKnightMoves.add(move);
		}
		if (col < 7 && row > 1
				&& board.isEmptyOrOpponent(Square.get(col + 1, row - 2))) {
			Move move = new Move(square, Square.get(col + 1, row - 2));
			possibleKnightMoves.add(move);
		}
		if (col > 0 && row > 1
				&& board.isEmptyOrOpponent(Square.get(col - 1, row - 2))) {
			Move move = new Move(square, Square.get(col - 1, row - 2));
			possibleKnightMoves.add(move);
		}
		if (col > 1 && row > 0
				&& board.isEmptyOrOpponent(Square.get(col - 2, row - 1))) {
			Move move = new Move(square, Square.get(col - 2, row - 1));
			possibleKnightMoves.add(move);
		}
		if (col > 1 && row < 7
				&& board.isEmptyOrOpponent(Square.get(col - 2, row + 1))) {
			Move move = new Move(square, Square.get(col - 2, row + 1));
			possibleKnightMoves.add(move);
		}

		return possibleKnightMoves;
	}

	/**
	 * Get possible moves for Bishop
	 * 
	 * @param board
	 * @param square
	 * @return
	 */
	private static ArrayList<Move> getPossibleBishopMoves(Board board, Square square) {
		ArrayList<Move> possibleBishopMoves = new ArrayList<Move>();
		int col = square.getCol();
		int row = square.getRow();
		int i;

		// Up-right moves
		for (i = 1; i < 8; i++) {
			if ((col + i) < 8 && (row + i) < 8
					&& board.isEmptyOrOpponent(Square.get(col + i, row + i))) {
				Move move = new Move(square, Square.get(col + i,
						row + i));
				possibleBishopMoves.add(move);
				if (!board.isSquareEmpty(Square.get(col + i, row + i))) {
					i = 999;
				}
			} else {
				i = 999;
			}
		}

		// Down-right moves
		for (i = 1; i < 8; i++) {
			if ((col + i) < 8 && (row - i) > -1
					&& board.isEmptyOrOpponent(Square.get(col + i, row - i))) {
				Move move = new Move(square, Square.get(col + i,
						row - i));
				possibleBishopMoves.add(move);
				if (!board.isSquareEmpty(Square.get(col + i, row - i))) {
					i = 999;
				}
			} else {
				i = 999;
			}
		}

		// Down-left moves
		for (i = 1; i < 8; i++) {
			if ((col - i) > -1 && (row - i) > -1
					&& board.isEmptyOrOpponent(Square.get(col - i, row - i))) {
				Move move = new Move(square, Square.get(col - i,
						row - i));
				possibleBishopMoves.add(move);
				if (!board.isSquareEmpty(Square.get(col - i, row - i))) {
					i = 999;
				}
			} else {
				i = 999;
			}
		}
		// Up-left moves
		for (i = 1; i < 8; i++) {
			if ((col - i) > -1 && (row + i) < 8
					&& board.isEmptyOrOpponent(Square.get(col - i, row + i))) {
				Move move = new Move(square, Square.get(col - i,
						row + i));
				possibleBishopMoves.add(move);
				if (!board.isSquareEmpty(Square.get(col - i, row + i))) {
					i = 999;
				}
			} else {
				i = 999;
			}
		}

		return possibleBishopMoves;
	}

	/**
	 * Get possible moves for Pawn
	 * 
	 * @param board
	 * @param square
	 * @return
	 */
	private static ArrayList<Move> getPossiblePawnMoves(Board board, Square square) {
		ArrayList<Move> possiblePawnMoves = new ArrayList<Move>();
		Piece piece = board.getPiece(square);
		int col = square.getCol();
		int row = square.getRow();

		if (piece == Piece.WP && row < 7) {
			// Pawn's move
			if (board.getPiece(Square.get(col, row + 1)) == Piece.EMPTY) {
				Move move = new Move(square, Square.get(col,
						row + 1));
				possiblePawnMoves.add(move);
			}
			if (row == 1
					&& (board.getPiece(Square.get(col, row + 1)) == Piece.EMPTY)
					&& (board.getPiece(Square.get(col, row + 2)) == Piece.EMPTY)) {
				Move move = new Move(square, Square.get(col,
						row + 2));
				possiblePawnMoves.add(move);
			}
			// Pawn capture
			if (col < 7
					&& board.isPieceBlack(board.getPiece(Square.get(col + 1,
							row + 1)))) {
				Move move = new Move(square, Square.get(col + 1,
						row + 1));
				possiblePawnMoves.add(move);
			}
			if (col > 0
					&& board.isPieceBlack(board.getPiece(Square.get(col - 1,
							row + 1)))) {
				Move move = new Move(square, Square.get(col - 1,
						row + 1));
				possiblePawnMoves.add(move);
			}
			// TODO implement "en passant"
			
		}
		if (piece == Piece.BP && row > 0) {
			// Pawn's move
			if (board.getPiece(Square.get(col, row - 1)) == Piece.EMPTY) {
				Move move = new Move(square, Square.get(col,
						row - 1));
				possiblePawnMoves.add(move);
			}
			if (row == 6
					&& (board.getPiece(Square.get(col, row - 1)) == Piece.EMPTY)
					&& (board.getPiece(Square.get(col, row - 2)) == Piece.EMPTY)) {
				Move move = new Move(square, Square.get(col,
						row - 2));
				possiblePawnMoves.add(move);
			}
			// Pawn capture
			if (col < 7
					&& board.isPieceWhite(board.getPiece(Square.get(col + 1,
							row - 1)))) {
				Move move = new Move(square, Square.get(col + 1,
						row - 1));
				possiblePawnMoves.add(move);
			}
			if (col > 0
					&& board.isPieceWhite(board.getPiece(Square.get(col - 1,
							row - 1)))) {
				Move move = new Move(square, Square.get(col - 1,
						row - 1));
				possiblePawnMoves.add(move);
			}
			// TODO implement "en passant"
		}

		return possiblePawnMoves;
	}

	/**
	 * Returns true is the move from c1,r1 to c2,r2 is possible
	 * 
	 * @param board
	 * @param move
	 * @return
	 */
	public boolean isPossibleMove(Board board, Move move) {
		ArrayList<Move> possibleMoves = getPossibleMoves(board, move.begin);
		for (int i = 0; i < possibleMoves.size(); i++) {
			if (possibleMoves.get(i).isEqual(move)) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * 
	 * @param board
	 * @param move
	 * @return
	 */
	private static boolean isKingExposed(Board board, Move move) {
		Board testBoard = new Board(board);
		testBoard.Play(move);
		if(isCheck(testBoard)){return true;}
		return false;
	}
	
	/**
	 * Get legal moves
	 * @param board
	 * @return
	 */
	public static ArrayList<Move> getLegalMoves(Board board) {
		ArrayList<Move> possibleMoves = getPossibleMoves(board);
		ArrayList<Move> legalMoves = new ArrayList<Move>();
		for(Move m : possibleMoves){
			if(!isKingExposed(board, m)){
				legalMoves.add(m);
			}
		}
		return legalMoves;
	}

	/**
	 * Return if the move is legal
	 * @param board
	 * @param move
	 * @return
	 */
	public static boolean isLegalMove(Board board, Move move) {
		ArrayList<Move> possibleMoves = getPossibleMoves(board, move.begin);
		for(Move m : possibleMoves){
			if(m.end == move.end){
				if(!isKingExposed(board, m)){return true;}
			}
		}
		return false;
	}
}
